<template>
  <div class="main">
    <div class="banner-1">
      <div class="slider" :style="`--quantity: ${state.arr.length}`" ref="ref_slider1">
        <template v-for="(item, index) in state.arr">
          <div class="item __hover" :style="`--position: ${index + 1}`">
            <div class="item-content" :style="{backgroundColor: $czrUtil.randomColor(1)}" :data-content="item"></div>
          </div>
        </template>
      </div>
    </div>
    <template v-for="(item, index) in state.arr">
      <el-button @click="rotateTo(index)">旋转至{{item}}</el-button>
    </template>
    <div class="banner-2">
      <div class="slider" :style="`--quantity: ${state.arr.length}`" ref="ref_slider2">
        <template v-for="(item, index) in state.arr">
          <div class="item __hover" :style="`--position: ${index + 1}`" @click="rotateTo(index)" @mouseenter="rotatePaused" @mouseleave="rotateStart">
            <div class="item-content" :style="{backgroundColor: $czrUtil.randomColor(1)}" :data-content="item"></div>
          </div>
        </template>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import {getCurrentInstance, onBeforeUnmount, onMounted, onUnmounted, reactive, ref} from "vue";

const {proxy} = getCurrentInstance()
const state: any = reactive({
  arr: [1, 2, 3, 4, 5, 6, 7, 8, 9],
  rotatePaused: false,  //  停止旋转标识
  animate: null,  //  旋转动画id，清除用
  startTime: null,  //  开始动画时间
  progress: 0,  //  动画进度
  pausedTime: null, //  暂停时间
  pausedTimeValue: 0, //  暂停到开始时间差值
  degOffset: 0, //  点击切换选项时，旋转角度偏移量
  transitionDelay: null,  //  切换时的过渡复原方法，避免影响动画效果
  rotateDelay: null,  //  恢复旋转的防抖处理
  currentIndex: 0,  //  当前旋转项索引
  transitionTime: 2,  //  每一次旋转过渡的时长，秒
})
const ref_slider1 = ref()
const ref_slider2 = ref()
const rotateTo = (index) => {
  // 已经在切换时，不触发
  if (state.transitionDelay) {
    return
  }
  const step = Math.min(Math.abs(index - state.currentIndex), index > state.currentIndex ? Math.abs(state.arr.length - index) + state.currentIndex : Math.abs(state.arr.length - state.currentIndex) + index) //  两位置间最小步长
  let direction = 1 //  目标在右为-1，目标在左为1
  if (index > state.currentIndex && step === Math.abs(index - state.currentIndex)) {  //  如  3 -> 6
    direction = -1
  } else if (index > state.currentIndex && step < Math.abs(index - state.currentIndex)) {  //  如 1 -> 9
    direction = 1
  } else if (index < state.currentIndex && step === Math.abs(index - state.currentIndex)) { //  如 9 -> 7
    direction = 1
  } else if (index < state.currentIndex && step < Math.abs(index - state.currentIndex)) { //  如 9 -> 2
    direction = -1
  }
  clearTimeout(state.rotateDelay) //  切换时，取消恢复旋转延时方法
  ref_slider2.value.style.transition = state.transitionTime + 's' //  主动切换时，添加过渡样式
  ref_slider2.value.style.transform = `perspective(1000px) rotateX(-10deg) rotateY(${-1 * state.currentIndex * 360 / state.arr.length + direction * step * 360 / state.arr.length}deg)`
  state.transitionDelay = setTimeout(() => {
    state.degOffset = index * 360 / state.arr.length
    state.currentIndex = index
    ref_slider2.value.style.transition = 'unset'
    ref_slider2.value.style.transform = `perspective(1000px) rotateX(-10deg) rotateY(${-1 * state.degOffset}deg)`
    state.transitionDelay = null
  }, state.transitionTime * 1000)
  state.rotatePaused = true //  暂停旋转
  cancelAnimationFrame(state.animate)
  state.animate = null
  state.startTime = null
  state.pausedTime = null
  state.pausedTimeValue = 0
}
const rotateStart = () => {
  //  切换时，不触发
  if (!state.transitionDelay) {
    //  暂停时长误差
    if (state.pausedTime) {
      state.pausedTimeValue += new Date().getTime() - state.pausedTime
      state.pausedTime = null
    }
    state.rotatePaused = false
    const animate = (t) => {
      if (!state.rotatePaused) {
        if (!state.startTime) {
          state.startTime = t
          state.pausedTimeValue = 0
        }
        const e = t - state.startTime - (state.pausedTimeValue || 0)
        state.progress = e / (state.transitionTime * state.arr.length * 1000) % 1
        ref_slider2.value.style.transform = `perspective(1000px) rotateX(-10deg) rotateY(${-1 * (360 * state.progress + state.degOffset)}deg)`
        state.currentIndex = Math.floor((360 * state.progress + state.degOffset) / (360 / state.arr.length))
      }
      state.animate = requestAnimationFrame(animate)
    }
    state.animate = requestAnimationFrame(animate)
  } else {  //  恢复旋转防抖
    clearTimeout(state.rotateDelay)
    state.rotateDelay = setTimeout(rotateStart, 500)
  }
}
const rotatePaused = () => {
  clearTimeout(state.rotateDelay)
  state.pausedTime = new Date().getTime()
  state.rotatePaused = true
  cancelAnimationFrame(state.animate)
  state.animate = null
}
onMounted(() => {
  rotateStart()
})
onBeforeUnmount(() => {
  cancelAnimationFrame(state.animate)
})
</script>

<style lang="scss" scoped>
.main {
  width: 100%;
  height: 100%;
  .banner-1 {
    width: 100%;
    height: 50%;
    text-align: center;
    overflow: hidden;
    position: relative;
    border: 1px solid goldenrod;
    .slider {
      border: 1px solid red;
      position: absolute;
      width: 100px;
      height: 150px;
      top: 30%;
      left: calc(50% - 100px);
      transform-style: preserve-3d;
      transition: 2s;
      transform: perspective(1000px) rotateX(-10deg) rotateY(150deg);
      animation-name: autoRun;
      animation-duration: 20s;
      animation-timing-function: linear;
      animation-iteration-count: infinite;
      animation-play-state: running;
      @keyframes autoRun {
        from {
          transform: perspective(1000px) rotateX(-10deg) rotateY(0deg);
        } to {
          transform: perspective(1000px) rotateX(-10deg) rotateY(-360deg);
        }
      }
      &:hover {
        animation-play-state: paused;
      }
      .item {
        position: absolute;
        inset: 0;
        transform:
          rotateY(calc((var(--position) - 1) * (360 / var(--quantity)) * 1deg))
          translateZ(300px);
        -webkit-box-reflect: below 5px linear-gradient(transparent 50%, rgba(0, 0, 0, 0.3));
        backface-visibility: hidden;
        .item-content {
          width: 100%;
          height: 100%;
          display: flex;
          align-items: center;
          justify-content: center;
          font-size: 100px;
          &:after {
            content: attr(data-content);
          }
        }
      }
    }
  }
  .banner-2 {
    width: 100%;
    height: 50%;
    text-align: center;
    overflow: hidden;
    position: relative;
    border: 1px solid goldenrod;
    .slider {
      border: 1px solid red;
      position: absolute;
      width: 100px;
      height: 150px;
      top: 30%;
      left: calc(50% - 100px);
      transform-style: preserve-3d;
      //transition: 2s;
      transform: perspective(1000px) rotateX(-10deg) rotateY(0deg);
      .item {
        position: absolute;
        inset: 0;
        transform:
          rotateY(calc((var(--position) - 1) * (360 / var(--quantity)) * 1deg))
          translateZ(300px);
        .item-content {
          width: 100%;
          height: 100%;
          display: flex;
          align-items: center;
          justify-content: center;
          font-size: 100px;
          &:after {
            content: attr(data-content);
          }
        }
      }
    }
  }
}
</style>
